﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AgCIM.Tools.SpatialEncoding.Beidou
{
    public class BeidouSpatialEncodingHeightCode
    {
        private readonly int[] indexNum = { 0, 6, 9, 10, 14, 18, 19, 22, 25, 28, 31 };

        /// <summary>
        /// 得到建筑高度编码（推荐）
        /// </summary>
        /// <param name="levelValue">层级值</param>
        /// <param name="level">层级</param>
        /// <param name="height">高度</param>
        /// <returns>北斗高度编码</returns>
        public HeightCodeObject GetHeightBeidouCode(double levelValue, int level, double height)
        {
            var meshInfo = this.HeightComputeFormula(levelValue, height);
            var result = this.HandleBeidouHeightCode(level, meshInfo.heightMeshNum, meshInfo.isDown);
            return result;
        }

        /// <summary>
        /// 获得全部的高度码
        /// </summary>
        /// <param name="height">高度</param>
        /// <returns>北斗高度编码</returns>
        public HeightCodeObject GetFullHeightBeidouCode(double height)
        {
            return this.GetHeightBeidouCode(1.0 / (2048.0 * 60.0 * 60.0), 10, height);
        }

        /// <summary>
        /// 高度网格计算公式法
        /// </summary>
        /// <param name="levelValue">层级值</param>
        /// <param name="height">高度</param>
        /// <returns>高度网格信息</returns>
        private (int heightMeshNum, bool isDown) HeightComputeFormula(double levelValue, double height)
        {
            const double cetaZero = Math.PI / 180.0;
            const double r0 = 6378137.0;
            bool isDown = false;

            if (height < 0)
            {
                height = -height;
                isDown = true;
            }

            double n = (1.0 / levelValue) * (Math.Log10((height + r0) / r0) / Math.Log10(1 + cetaZero));
            return (heightMeshNum: (int)n, isDown: isDown);
        }        

        /// <summary>
        /// 获取高度编码的另一种方法
        /// </summary>
        /// <param name="levelValue">层级对应网格大小</param>
        /// <param name="level">层级</param>
        /// <param name="height">具体高度值</param>
        /// <returns>北斗高度编码</returns>
        public HeightCodeObject GetHeightBeidouCodeAnOtherMethod(double levelValue, int level, double height)
        {
            bool isDown = false;
            if (height < 0)
            {
                height = -height;
                isDown = true;
            }

            double ceta = (levelValue * Math.PI) / 180.0;
            const double ro = 6378137.0;
            double H = 0;
            int n = 0;
            int heightNum = (int)Math.Pow(2, this.indexNum[level]);

            for (int i = 0; i < heightNum; i++)
            {
                double h = ro * ceta * Math.Pow(1 + ceta, i);
                H += h;
                if (height <= H)
                {
                    n = i;
                    break;
                }
            }

            var code = this.HandleBeidouHeightCode(level, n, isDown);
            return code;
        }

        /// <summary>
        /// 处理北斗高度编码
        /// </summary>
        /// <param name="index">对应层级</param>
        /// <param name="n">对应到第几个网格</param>
        /// <param name="isDown">是否是往下面延申的</param>
        /// <returns>北斗高度编码对象</returns>
        private HeightCodeObject HandleBeidouHeightCode(int index, int n, bool isDown)
        {
            int a = n;
            int[] bit32 = new int[32];
            string binaryString = Convert.ToString(a, 2);

            for (int i = 0; i < binaryString.Length; i++)
            {
                bit32[31 - i] = binaryString[binaryString.Length - 1 - i] - '0';
            }

            int[] bitCode = bit32.Skip(bit32.Length - (this.indexNum[index] + 1)).ToArray();
            List<string> heightCode = ConvertHeightByBit32(bit32);
            string[] heicodeResult = heightCode.Skip(heightCode.Count - 1 - index).Take(index + 1).ToArray();

            if (isDown)
            {
                heicodeResult[0] = "1";
            }

            HeightCodeObject codeObject = new HeightCodeObject() { CodeBit = bitCode, CodeTrans = heicodeResult };

            return codeObject;
        }

        /// <summary>
        /// 高度32位bit码转换为对应高度码
        /// </summary>
        /// <param name="bit32">32位的bit码</param>
        /// <returns>高度码数组</returns>
        private List<string> ConvertHeightByBit32(int[] bit32)
        {
            int a0 = bit32[0];
            int a1a2 =
                bit32[6] * (int)Math.Pow(2, 0) +
                bit32[5] * (int)Math.Pow(2, 1) +
                bit32[4] * (int)Math.Pow(2, 2) +
                bit32[3] * (int)Math.Pow(2, 3) +
                bit32[2] * (int)Math.Pow(2, 4) +
                bit32[1] * (int)Math.Pow(2, 5);
            int a3 =
                bit32[9] * (int)Math.Pow(2, 0) +
                bit32[8] * (int)Math.Pow(2, 1) +
                bit32[7] * (int)Math.Pow(2, 2);
            int a4 = bit32[10];
            int a5 =
                bit32[14] * (int)Math.Pow(2, 0) +
                bit32[13] * (int)Math.Pow(2, 1) +
                bit32[12] * (int)Math.Pow(2, 2) +
                bit32[11] * (int)Math.Pow(2, 3);
            int a6 =
                bit32[18] * (int)Math.Pow(2, 0) +
                bit32[17] * (int)Math.Pow(2, 1) +
                bit32[16] * (int)Math.Pow(2, 2) +
                bit32[15] * (int)Math.Pow(2, 3);
            int a7 = bit32[19];
            int a8 =
                bit32[22] * (int)Math.Pow(2, 0) +
                bit32[21] * (int)Math.Pow(2, 1) +
                bit32[20] * (int)Math.Pow(2, 2);
            int a9 =
                bit32[25] * (int)Math.Pow(2, 0) +
                bit32[24] * (int)Math.Pow(2, 1) +
                bit32[23] * (int)Math.Pow(2, 2);
            int a10 =
                bit32[28] * (int)Math.Pow(2, 0) +
                bit32[27] * (int)Math.Pow(2, 1) +
                bit32[26] * (int)Math.Pow(2, 2);
            int a11 =
                bit32[31] * (int)Math.Pow(2, 0) +
                bit32[30] * (int)Math.Pow(2, 1) +
                bit32[29] * (int)Math.Pow(2, 2);

            string a1a2Str = a1a2 < 10 ? "0" + a1a2 : a1a2.ToString();

            string a5Hex = a5.ToString("X").ToUpper();
            string a6Hex = a6.ToString("X").ToUpper();

            var heightCode = new List<string>
            {
                a0.ToString(),
                a1a2Str,
                a3.ToString(),
                a4.ToString(),
                a5Hex,
                a6Hex,
                a7.ToString(),
                a8.ToString(),
                a9.ToString(),
                a10.ToString(),
                a11.ToString()
            };

            return heightCode;
        }

        /// <summary>
        /// 把10进制的高度变成这个层次下的基数
        /// 如把[0, 4, 0]变成4*8=32;因为最后一个有8个值，3个比特
        /// 如heightOxArray=[0,0,0,0,0,0,1,0],则得到8;
        /// </summary>
        /// <param name="heightOxArray">10进制的高度数组</param>
        /// <returns>计算后的基数</returns>
        public int ConvertHeightOxToCount(int[] heightOxArray)
        {
            int sum = 0;
            int[] strip = { 1, 6, 3, 1, 4, 4, 1, 3, 3, 3, 3 };
            int stripLen = 1;

            for (int i = heightOxArray.Length - 1; i >= 0; i--)
            {
                sum += heightOxArray[i] * stripLen;
                stripLen *= (int)Math.Pow(2, strip[i]);
            }

            return sum;
        }
    }

    public class HeightCodeObject
    {
        public int[] CodeBit { get; set; }
        public string[] CodeTrans { get; set; }
    }
}
